﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Collections.Specialized;
using System.Web;
using System.Web.Caching;
using System.Web.Routing;
using System.Web.Mvc;

namespace Sencha.Direct
{
	public abstract partial class Router : DirectController
	{
		public class RoutingContext
		{
			private static string[] EmptyParameters = new string[0];
			private static string[] EmptyArguments = new string[0];

			private IDictionary<string, string> m_arguments;

			public string Controller { get; private set; }
			public string Action { get; private set; }
			public ICollection<string> Arguments { get; private set; }
			public ICollection<string> Parameters { get; private set; }
			public string Type { get; private set; }
			public bool IsForm { get; private set; }
			public bool IsUpload { get; private set; }
			public int TransactionId { get; private set; }

			public object RequestResult { get; set; }

			internal RoutingContext(IncomingRequest request)
			{
				if (request == null)
					throw new ArgumentNullException("request");

				m_arguments = request.Arguments;

				this.Controller = request.Controller;
				this.Action = request.Action;
				this.Arguments = request.Arguments != null ? request.Arguments.Values : EmptyArguments;
				this.Parameters = request.Arguments != null ? request.Arguments.Keys : EmptyParameters;
				this.Type = request.Type;
				this.IsForm = request.IsForm;
				this.IsUpload = request.IsUpload;
				this.TransactionId = request.TransactionId;
			}

			internal IEnumerable<KeyValuePair<string, string>> EnumerateArguments()
			{
				return m_arguments;
			}

			public override string ToString()
			{
				var sb = new StringBuilder();
				sb.Append(this.Controller)
					.Append("/")
					.Append(this.Action)
					.Append("(");

				var ct = 0;
				foreach (var arg in this.EnumerateArguments())
				{
					sb.Append(arg.Key)
						.Append("=")
						.Append(arg.Value);

					if (ct < (this.Parameters.Count - 1))
						sb.Append(", ");
				}

				sb.Append(")")
					.Append(", type=")
					.Append(this.Type)
					.Append(", tid=")
					.Append(this.TransactionId);

				return sb.ToString();
			}
		}

		private sealed class FakeHttpContext : HttpContextBase
		{
			private HttpContextBase context;
			private HttpRequestBase request;
			private HttpResponseBase responce;

			public override HttpResponseBase Response
			{
				get
				{
					return responce ?? context.Response;
				}
			}
			public override HttpRequestBase Request
			{
				get
				{
					return request ?? context.Request;
				}
			}
			public override Exception[] AllErrors { get { return this.context.AllErrors; } }

			public override HttpApplicationStateBase Application { get { return this.context.Application; } }
			public override HttpApplication ApplicationInstance { get { return this.context.ApplicationInstance; } set { this.context.ApplicationInstance = value; } }
			public override Cache Cache { get { return this.context.Cache; } }
			public override IHttpHandler CurrentHandler { get { return this.context.CurrentHandler; } }
			public override RequestNotification CurrentNotification { get { return this.context.CurrentNotification; } }
			public override Exception Error { get { return this.context.Error; } }
			public override IHttpHandler Handler { get { return this.context.Handler; } set { this.context.Handler = value; } }
			public override bool IsCustomErrorEnabled { get { return this.context.IsCustomErrorEnabled; } }
			public override bool IsDebuggingEnabled { get { return this.context.IsDebuggingEnabled; } }
			public override bool IsPostNotification { get { return this.context.IsDebuggingEnabled; } }
			public override IDictionary Items { get { return this.context.Items; } }
			public override IHttpHandler PreviousHandler { get { return this.context.PreviousHandler; } }
			public override System.Web.Profile.ProfileBase Profile { get { return this.context.Profile; } }
			public override HttpServerUtilityBase Server { get { return this.context.Server; } }
			public override HttpSessionStateBase Session { get { return this.context.Session; } }
			public override bool SkipAuthorization { get { return this.context.SkipAuthorization; } set { this.context.SkipAuthorization = value; } }
			public override DateTime Timestamp { get { return this.context.Timestamp; } }
			public override TraceContext Trace { get { return this.context.Trace; } }
			public override System.Security.Principal.IPrincipal User { get { return this.context.User; } set { this.context.User = value; } }

			public FakeHttpContext(HttpContextBase context, HttpRequestBase request, HttpResponseBase responce)
			{
				if (context == null)
					throw new ArgumentNullException("context");

				this.context = context;
				this.request = request;
				this.responce = responce;
			}

			public override void AddError(Exception errorInfo)
			{
				this.context.AddError(errorInfo);
			}
			public override void ClearError()
			{
				this.context.ClearError();
			}
			public override object GetGlobalResourceObject(string classKey, string resourceKey)
			{
				return this.context.GetGlobalResourceObject(classKey, resourceKey);
			}
			public override object GetGlobalResourceObject(string classKey, string resourceKey, System.Globalization.CultureInfo culture)
			{
				return this.context.GetGlobalResourceObject(classKey, resourceKey, culture);
			}
			public override object GetLocalResourceObject(string virtualPath, string resourceKey)
			{
				return this.context.GetLocalResourceObject(virtualPath, resourceKey);
			}
			public override object GetLocalResourceObject(string virtualPath, string resourceKey, System.Globalization.CultureInfo culture)
			{
				return this.context.GetLocalResourceObject(virtualPath, resourceKey, culture);
			}
			public override object GetSection(string sectionName)
			{
				return this.context.GetSection(sectionName);
			}
			public override object GetService(Type serviceType)
			{
				return ((IServiceProvider)this.context).GetService(serviceType);
			}

			public override void RewritePath(string path)
			{
				throw new NotSupportedException();
			}
			public override void RewritePath(string path, bool rebaseClientPath)
			{
				throw new NotSupportedException();
			}
			public override void RewritePath(string filePath, string pathInfo, string queryString)
			{
				throw new NotSupportedException();
			}
			public override void RewritePath(string filePath, string pathInfo, string queryString, bool setClientFilePath)
			{
				throw new NotSupportedException();
			}
		}
		private sealed class FakeHttpRequest : HttpRequestBase
		{
			// Fields
			private HttpRequestBase httpRequest;
			private static MemoryStream EmptyStream = new System.IO.MemoryStream(new byte[0], false);
			private string rawUrl = string.Empty;
			private string httpMethod = null;

			public FakeHttpRequest(HttpRequestBase request)
			{
				if (request == null)
					throw new ArgumentNullException("request");

				this.httpRequest = request;
				this.rawUrl = request.RawUrl;
			}

			public override string[] AcceptTypes { get { return this.httpRequest.AcceptTypes; } }
			public override string AnonymousID
			{
				get
				{
					return this.httpRequest.AnonymousID;
				}
			}
			public override string ApplicationPath
			{
				get
				{
					return this.httpRequest.ApplicationPath;
				}
			}
			public override string AppRelativeCurrentExecutionFilePath
			{
				get
				{
					return this.AppRelativeCurrentExecutionFilePathOverride;
				}
			}
			public string AppRelativeCurrentExecutionFilePathOverride { get; set; }
			public override HttpBrowserCapabilitiesBase Browser
			{
				get
				{
					return this.httpRequest.Browser;
				}
			}
			public override HttpClientCertificate ClientCertificate
			{
				get
				{
					return this.httpRequest.ClientCertificate;
				}
			}
			public override Encoding ContentEncoding
			{
				get
				{
					return this.httpRequest.ContentEncoding;
				}
				set
				{
					this.httpRequest.ContentEncoding = value;
				}
			}
			public override int ContentLength
			{
				get
				{
					return this.httpRequest.ContentLength;
				}
			}
			public override string ContentType
			{
				get
				{
					return this.httpRequest.ContentType;
				}
				set
				{
					this.httpRequest.ContentType = value;
				}
			}
			public override HttpCookieCollection Cookies
			{
				get
				{
					return this.httpRequest.Cookies;
				}
			}
			public override string CurrentExecutionFilePath
			{
				get
				{
					return this.httpRequest.CurrentExecutionFilePath;
				}
			}
			public override string FilePath
			{
				get
				{
					return this.FilePathOverride;
				}
			}
			public string FilePathOverride { get; set; }
			public override HttpFileCollectionBase Files
			{
				get
				{
					return this.httpRequest.Files;
				}
			}
			public override Stream Filter
			{
				get
				{
					return this.httpRequest.Filter;
				}
				set
				{
					this.httpRequest.Filter = value;
				}
			}
			public override NameValueCollection Form
			{
				get
				{
					return this.FormOverride;
				}
			}
			public NameValueCollection FormOverride;
			public override NameValueCollection Headers
			{
				get
				{
					return this.httpRequest.Headers;
				}
			}
			public override string HttpMethod
			{
				get
				{
					if (string.IsNullOrEmpty(httpMethod))
						return httpMethod;
					return this.httpRequest.HttpMethod;
				}
			}
			public string HttpMethodOverride
			{
				set
				{
					httpMethod = value;
				}
			}
			public override Stream InputStream
			{
				get
				{
					return EmptyStream;
				}
			}
			public override bool IsAuthenticated
			{
				get
				{
					return this.httpRequest.IsAuthenticated;
				}
			}
			public override bool IsLocal
			{
				get
				{
					return this.httpRequest.IsLocal;
				}
			}
			public override bool IsSecureConnection
			{
				get
				{
					return this.httpRequest.IsSecureConnection;
				}
			}
			public override string this[string key]
			{
				get
				{
					return this.httpRequest[key];
				}
			}
			public override System.Security.Principal.WindowsIdentity LogonUserIdentity
			{
				get
				{
					return this.httpRequest.LogonUserIdentity;
				}
			}
			public override NameValueCollection Params
			{
				get
				{
					return this.ParamsOverride;
				}
			}
			public NameValueCollection ParamsOverride;
			public override string Path
			{
				get
				{
					return this.PathOverride;
				}
			}
			public string PathOverride { get; set; }
			public override string PathInfo
			{
				get
				{
					return this.httpRequest.PathInfo;
				}
			}
			public override string PhysicalApplicationPath
			{
				get
				{
					return this.PhysicalApplicationPathOverride;
				}
			}
			public string PhysicalApplicationPathOverride { get; set; }
			public override string PhysicalPath
			{
				get
				{
					return this.PhysicalPathOverride;
				}
			}
			public string PhysicalPathOverride { get; set; }
			public override NameValueCollection QueryString
			{
				get
				{
					return this.QueryStringOverride;
				}
			}
			public NameValueCollection QueryStringOverride;
			public override string RawUrl
			{
				get
				{
					return this.RawUrlOverride;
				}
			}
			public string RawUrlOverride
			{
				get { return rawUrl; }
				set
				{
					this.rawUrl = value;
					this.UrlOverride = new Uri(rawUrl);
					this.PathOverride = this.Url.LocalPath;
					this.PhysicalPathOverride = this.Path;
					this.PhysicalApplicationPathOverride = this.Path;
					this.AppRelativeCurrentExecutionFilePathOverride = "~" + this.Path;
				}
			}
			public override string RequestType
			{
				get
				{
					return this.httpRequest.RequestType;
				}
				set
				{
					this.httpRequest.RequestType = value;
				}
			}
			public override NameValueCollection ServerVariables
			{
				get
				{
					return this.httpRequest.ServerVariables;
				}
			}
			public override int TotalBytes
			{
				get
				{
					return this.httpRequest.TotalBytes;
				}
			}
			public override Uri Url
			{
				get
				{
					return UrlOverride;
				}
			}
			public Uri UrlOverride { get; set; }
			public override Uri UrlReferrer
			{
				get
				{
					return this.httpRequest.UrlReferrer;
				}
			}
			public override string UserAgent
			{
				get
				{
					return this.httpRequest.UserAgent;
				}
			}
			public override string UserHostAddress
			{
				get
				{
					return this.httpRequest.UserHostAddress;
				}
			}
			public override string UserHostName
			{
				get
				{
					return this.httpRequest.UserHostName;
				}
			}
			public override string[] UserLanguages
			{
				get
				{
					return this.httpRequest.UserLanguages;
				}
			}

			public override byte[] BinaryRead(int count)
			{
				return EmptyStream.GetBuffer();
			}
			public override int[] MapImageCoordinates(string imageFieldName)
			{
				return this.httpRequest.MapImageCoordinates(imageFieldName);
			}
			public override string MapPath(string virtualPath)
			{
				return this.httpRequest.MapPath(virtualPath);
			}
			public override string MapPath(string virtualPath, string baseVirtualDir, bool allowCrossAppMapping)
			{
				return this.httpRequest.MapPath(virtualPath, baseVirtualDir, allowCrossAppMapping);
			}
			public override void SaveAs(string filename, bool includeHeaders)
			{
				var stream = new FileStream(filename, FileMode.Create);
				try
				{
					if (includeHeaders)
					{
						TextWriter writer = new StreamWriter(stream);
						writer.Write(this.HttpMethod + " " + this.Path);
						string queryStringText = this.Url.Query;
						if (!string.IsNullOrEmpty(queryStringText))
							writer.Write("?" + queryStringText);

						writer.Write(this.Headers.ToString());
						writer.Write("\r\n");
						writer.Flush();
					}
					stream.Flush();
				}
				finally
				{
					stream.Close();
				}
			}
			public override void ValidateInput()
			{
				this.httpRequest.ValidateInput();
			}

			internal static void SetHttpMethod(HttpRequestBase httpRequestBase, ICollection<string> methods)
			{
				var fakeRequest = httpRequestBase as FakeHttpRequest;
				if( fakeRequest == null)
					return;

				if (methods.Contains("POST"))
					fakeRequest.HttpMethodOverride = "POST";
				else if (methods.Contains("GET"))
					fakeRequest.HttpMethodOverride = "GET";
				else
					fakeRequest.HttpMethodOverride = System.Linq.Enumerable.First(methods);
			}
		}
		private sealed class FakeHttpResponse : HttpResponseBase
		{
			private string contentType;
			private Encoding contentEncoding;
			private string charset;
			private NameValueCollection headers;
			private HttpCachePolicyBase cache;
			private Encoding headerEncoding;

			private MemoryStream output;
			private TextWriter writer;
			private HttpResponseBase httpResponse;

			public FakeHttpResponse(HttpResponseBase response, MemoryStream outputStream)
			{
				if (response == null)
					throw new ArgumentNullException("response");

				this.httpResponse = response;
				this.Status = "200 OK";
				this.SubStatusCode = 0;

				this.cache = new FakeHttpCachePolicy();

				this.output = outputStream ?? new MemoryStream(100 * 1024); // 100 kb
			}

			public override void AddCacheDependency(params CacheDependency[] dependencies)
			{

			}
			public override void AddCacheItemDependencies(System.Collections.ArrayList cacheKeys)
			{

			}
			public override void AddCacheItemDependencies(string[] cacheKeys)
			{

			}
			public override void AddCacheItemDependency(string cacheKey)
			{

			}
			public override void AddFileDependencies(System.Collections.ArrayList filenames)
			{

			}
			public override void AddFileDependencies(string[] filenames)
			{

			}
			public override void AddFileDependency(string filename)
			{

			}
			public override void AppendHeader(string name, string value)
			{
				throw new NotSupportedException(); // protect real response from changing headers
			}
			public override void AddHeader(string name, string value)
			{
				throw new NotSupportedException(); // protect real response from changing headers
			}
			public override void AppendCookie(HttpCookie cookie)
			{
				this.httpResponse.AppendCookie(cookie);
			}
			public override void AppendToLog(string param)
			{
				this.httpResponse.AppendToLog(param);
			}
			public override string ApplyAppPathModifier(string virtualPath)
			{
				throw new NotSupportedException();
			}
			public override void BinaryWrite(byte[] buffer)
			{
				throw new NotSupportedException();
			}
			public override void Clear()
			{
				output.SetLength(0);
			}
			public override void ClearContent()
			{
				this.httpResponse.ClearContent();
			}
			public override void ClearHeaders()
			{
				throw new NotSupportedException();
			}
			public override void Close()
			{
				this.End();
			}
			public override void DisableKernelCache()
			{
				throw new NotSupportedException();
			}
			public override void End()
			{
				this.Flush();
			}
			public override void Flush()
			{
				if (this.writer != null)
					writer.Flush();
			}
			public override void Pics(string value)
			{
				throw new NotSupportedException();
			}
			public override void Redirect(string url)
			{
				throw new NotSupportedException();
			}
			public override void Redirect(string url, bool endResponse)
			{
				throw new NotSupportedException();
			}
			public override void RemoveOutputCacheItem(string path)
			{
				HttpResponse.RemoveOutputCacheItem(path);
			}
			public override void SetCookie(HttpCookie cookie)
			{
				this.httpResponse.SetCookie(cookie);
			}
			public override void TransmitFile(string filename)
			{
				this.TransmitFile(filename, 0, -1);
			}
			public override void TransmitFile(string filename, long offset, long length)
			{
				if (filename == null)
					throw new ArgumentNullException("filename");


				using (var f = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read))
				{
					if (offset > 0)
						f.Seek(offset, SeekOrigin.Begin);
					if (length < 0)
						length = f.Length;

					output.Capacity += (int)f.Length;
					var b = new byte[10 * 1024];
					var r = 0;
					if (writer != null)
						writer.Flush();

					while ((r = f.Read(b, 0, (int)Math.Max(b.Length, length))) > 0 && length > 0)
					{
						output.Write(b, 0, r);
						length -= r;
					}
					f.Close();
				}
			}
			public override void Write(char ch)
			{
				this.Output.Write(ch);
				this.Output.Flush();
			}
			public override void Write(object obj)
			{
				this.Output.Write(obj);
				this.Output.Flush();
			}
			public override void Write(string s)
			{
				this.Output.Write(s);
				this.Output.Flush();
			}
			public override void Write(char[] buffer, int index, int count)
			{
				this.Output.Write(buffer, index, count);
				this.Output.Flush();
			}
			public override void WriteFile(string filename)
			{
				if (filename == null)
				{
					throw new ArgumentNullException("filename");
				}
				this.WriteFile(filename, false);
			}
			public override void WriteFile(string filename, bool readIntoMemory)
			{
				if (filename == null)
					throw new ArgumentNullException("filename");

				this.TransmitFile(filename, 0, -1);
			}
			[System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand, UnmanagedCode = true)]
			public override void WriteFile(IntPtr fileHandle, long offset, long size)
			{
				throw new NotSupportedException();
			}
			public override void WriteFile(string filename, long offset, long size)
			{
				if (filename == null)
					throw new ArgumentNullException("filename");
				if (size < 0)
					throw new ArgumentOutOfRangeException("size");
				if (size == 0)
					return;

				using (var f = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read))
				{
					output.Capacity += (int)size;
					f.Seek(offset, SeekOrigin.Begin);

					var b = new byte[10 * 1024];
					var r = 0;
					if (writer != null)
						writer.Flush();
					while ((r = f.Read(b, 0, b.Length)) > 0)
					{
						size -= r;
						if (size < 0)
							r -= (int)size;

						output.Write(b, 0, r);

						if (size <= 0)
							break;
					}
					f.Close();
				}
			}
			public override void WriteSubstitution(HttpResponseSubstitutionCallback callback)
			{
				throw new NotSupportedException(); // protect real response from changing headers
			}

			public override bool Buffer { get; set; } // protect real response from setting this flag
			public override bool BufferOutput { get; set; } // protect real response from setting this flag

			public override HttpCachePolicyBase Cache
			{
				get
				{
					return this.cache;
				}
			}
			public override string CacheControl { get; set; }
			public override string Charset { get { return charset ?? this.httpResponse.Charset; } set { charset = value; } }
			public override Encoding ContentEncoding { get { return contentEncoding ?? this.httpResponse.ContentEncoding; } set { contentEncoding = value; } }
			public override string ContentType { get { return contentType ?? this.httpResponse.ContentType; } set { contentType = value; } }
			public override HttpCookieCollection Cookies
			{
				get
				{
					return this.httpResponse.Cookies;
				}
			}
			public override int Expires
			{
				get
				{
					return this.httpResponse.Expires;
				}
				set
				{
					this.httpResponse.Expires = value;
				}
			}
			public override DateTime ExpiresAbsolute
			{
				get
				{
					return this.httpResponse.ExpiresAbsolute;
				}
				set
				{
					this.httpResponse.ExpiresAbsolute = value;
				}
			}
			public override Stream Filter
			{
				get
				{
					return null;
				}
				set
				{
					throw new NotSupportedException();
				}
			}
			public override Encoding HeaderEncoding { get { return headerEncoding ?? this.httpResponse.HeaderEncoding; } set { headerEncoding = value; } }
			public override NameValueCollection Headers
			{
				get
				{
					if (headers == null)
						headers = new NameValueCollection(this.httpResponse.Headers);
					return headers;
				}
			}
			public override bool IsClientConnected
			{
				get
				{
					return this.httpResponse.IsClientConnected;
				}
			}
			public override bool IsRequestBeingRedirected
			{
				get
				{
					return this.httpResponse.IsRequestBeingRedirected;
				}
			}
			public override Stream OutputStream
			{
				get
				{
					return output;
				}
			}
			public override TextWriter Output
			{
				get
				{
					if (this.writer == null)
						this.writer = new StreamWriter(output, this.ContentEncoding) { AutoFlush = true };
					return writer;
				}
			}
			public override string RedirectLocation
			{
				get
				{
					return null;
				}
				set
				{
					throw new NotSupportedException();
				}
			}
			public override string Status
			{
				get { return string.Concat(StatusCode, " ", StatusDescription); }
				set
				{
					int num = 200;
					string str = "OK";
					try
					{
						int index = value.IndexOf(' ');
						num = int.Parse(value.Substring(0, index), System.Globalization.CultureInfo.InvariantCulture);
						str = value.Substring(index + 1);
					}
					catch
					{
						throw new HttpException();
					}
					this.StatusCode = num;
					this.StatusDescription = str;
				}
			}
			public override int StatusCode { get; set; }
			public override string StatusDescription { get; set; }
			public override int SubStatusCode { get; set; }
			public override bool SuppressContent { get; set; } // protect real response from setting this flag
			public override bool TrySkipIisCustomErrors { get; set; } // protect real response from setting this flag
		}
		private sealed class FakeHttpCachePolicy : HttpCachePolicyBase
		{

		}

		private sealed class RouterActionInvoker : ControllerActionInvoker
		{
			private ControllerActionInvoker inner;

			public RouterActionInvoker()
			{
				base.Binders = new ModelBinderDictionary();
				base.Binders.DefaultBinder = JsonModelBinder.Default;
			}
		}
	}

	static class WebContextsExtentions
	{
		public static bool IsDirectRouted(this RequestContext context)
		{
			if (context == null)
				throw new NullReferenceException();

			return context.RouteData.DataTokens.ContainsKey(Router.TokenRoutedBy);
		}
	}
}
